//////////////////////////////////////////////////////////////////////////////////
//	 Copyright 2011 by Yosef Grabivker, ryuho@homenetworksoft.com
//
//   Licensed under the Apache License, Version 2.0 (the "License");
//   you may not use this file except in compliance with the License.
//   You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
//   Unless required by applicable law or agreed to in writing, software
//   distributed under the License is distributed on an "AS IS" BASIS,
//   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
//   See the License for the specific language governing permissions and
//   limitations under the License.
//////////////////////////////////////////////////////////////////////////////////

#include <wx/wx.h>
#include <sstream>
#include "mainwnd.h"
#include "logicmanager.h"
#include "rtevents.h"
#include "../common/MarkupSTL.h"
#include "utils.h"
#include "adslmodem.h"
#include "wirelesshw.h"
#include "shellcmdprocessor.h"

// /proc/net/expire_early can help expire udp connections early, but not found in DSL-2640U

CLogicManager::CLogicManager(CLinkGuard& link,  CCommandQueue& cmdQueue, CMainWnd& wnd):
	m_link(link), m_cmdQueue(cmdQueue), m_wMain(wnd), m_pModem(NULL), m_pAccessPoint(NULL),
	m_pProcCMD(NULL)
{
}

CLogicManager::~CLogicManager()
{
	if ( m_pModem )
	{
		delete m_pModem;
		m_pModem = NULL;
	}

	if ( m_pAccessPoint )
	{
		delete m_pAccessPoint;
		m_pAccessPoint = NULL;
	}

	if (m_pProcCMD)
	{
		delete m_pProcCMD;
		m_pProcCMD = NULL;
	}
}

void CLogicManager::Init(const std::string& strModemSettings, const std::string& strWirelessSettings)
{
	//Detect Shell
	m_pProcCMD = CShellCmdProcessor::CreateInstanse(m_link);

	if (!m_pProcCMD) THROW_EXC(cERShellNotComptble);

	COMMANDRESPONSE cmdOut;
	cmdOut.code = cCMDShellCaps;
	cmdOut.strData = m_pProcCMD->GetCaps();
	NotifyGUI( cmdOut );

	m_pProcCMD->Initialize();

	//Shell has integrated modem commands?
	m_pModem = m_pProcCMD->GetModem();

	if ( !m_pModem )
	{
		//Detect Modem
		m_pModem = CADSLModem::CreateInstanse(m_link);
	}

	if ( m_pModem )
	{
		std::string strCaps = m_pModem->GetCaps();

		COMMANDRESPONSE cmdOut;
		cmdOut.code = cCmdSetModemCaps;
		cmdOut.strData = strCaps;
		
		NotifyGUI( cmdOut );
		
		if ( !strModemSettings.empty() )
		{
			m_pModem->SetUpModem(strModemSettings);
		}
	}

	//Shell has integrated wireless commands?
	m_pAccessPoint = m_pProcCMD->GetAcessPoint();

	if ( !m_pAccessPoint )
	{
		//Wireless
		m_pAccessPoint = CWirelessHW::CreateInstanse(m_link);
	}

	if ( m_pAccessPoint )
	{
		std::string strCaps = m_pAccessPoint->GetCaps();

		COMMANDRESPONSE cmdOut;
		cmdOut.code = cCmdSetWirelessCaps;
		cmdOut.strData = strCaps;

		NotifyGUI( cmdOut );

		if ( !strWirelessSettings.empty() )
		{
			m_pAccessPoint->SetUp(strWirelessSettings);
		}
	}

	COMMANDRESPONSE cmdFinal;
	cmdFinal.code = cCMDRouterReady;
	NotifyGUI( cmdFinal );
}

//DO NOT catch exceptions here
//This will broke disconnect
//logic
bool CLogicManager::Tick()
{
	COMMANDREQUEST cmdIn;
	
	while( m_cmdQueue.GetRequest(cmdIn) )
	{
		if ( cCmdQuit == cmdIn.code )
		{
			//OutputDebugStr(_T("CLogicManager::Tick() cCmdQuit\n"));
			return false;
		}

		bool bNotify(false);
		COMMANDRESPONSE cmdOut;
		DispatchCmd(cmdIn, cmdOut, bNotify);

		if ( bNotify )
		{
			NotifyGUI( cmdOut );
		}
	
		wxThread::Sleep(10);
	}
	
	//Send tick to gui
	COMMANDRESPONSE cmdOut;
	cmdOut.code =  cCmdTick; 
	NotifyGUI( cmdOut );
		
	return true;
}

void CLogicManager::NotifyGUI( const COMMANDRESPONSE& cmdOut )
{
	m_cmdQueue.AddResponce( cmdOut );
	
	wxCommandEvent eventCustom(wxEVT_ON_ROUTER_THREAD);
	eventCustom.SetId(cmdOut.code);		
	wxPostEvent(&m_wMain, eventCustom);

	wxThread::Sleep(10);
}


void CLogicManager::DispatchCmd(const COMMANDREQUEST& cmd, COMMANDRESPONSE& cmdOut,
		bool& bNotify)
{
	cmdOut.subCode = cmd.subCode;
	cmdOut.code = cmd.code;

	//Generic
	switch( cmd.code )
	{
		case cCmdGetModemStats:
			OnGetAdslStats( cmdOut, bNotify);
			break;

		case cCmdExecuteModemXML:
			OnExecuteModemXML( cmd.subCode, cmd.strScript, cmdOut, bNotify);
		break;

		case cCmdGetWirelessStats:
			OnGetWirelessStat( cmdOut, bNotify);
		break;

		case cCmdExecuteWirelessXML:
			OnExecuteWirelessXML(cmd.strScript, cmdOut, bNotify);
		break;


		default:
		{
			if ( m_pProcCMD )
			{
				m_pProcCMD->DispatchCmd( cmd, cmdOut, bNotify  );
			}
		}
		break;
	}
}

//////////////////////////////////////////////////////////////////////////////////////////////
void CLogicManager::OnGetAdslStats(COMMANDRESPONSE& cmdOut, bool& bNotify)
{
	bNotify = false;

	if ( m_pModem )
	{
		cmdOut.code   = cCmdGetModemStats;
		cmdOut.strXML = m_pModem->GetModemStats(cmdOut.strData);

		if ( !cmdOut.strXML.empty() )
		{
			bNotify = true;
		}
	}
}

void CLogicManager::OnExecuteModemXML(CMDSUBCODE subCode, const wxString& strScript, COMMANDRESPONSE& cmdOut, bool& bNotify)
{
	bNotify = false;

	if ( m_pModem )
	{
		std::string strXML = (const char*)strScript.ToAscii();

		if ( m_pModem->ExecuteXML( strXML ) )
		{
			COMMANDRESPONSE cmd;
			cmd.subCode = subCode;
			cmd.code = cCmdExecuteModemXML;
			NotifyGUI( cmd );
		}

		//Get modem stats
		OnGetAdslStats(cmdOut, bNotify);
	}
}

void CLogicManager::OnGetWirelessStat(COMMANDRESPONSE& cmdOut, bool& bNotify)
{
	RTSTRING strData;

	bNotify = false;

	if ( m_pAccessPoint )
	{
		cmdOut.code = cCmdGetWirelessStats;
		cmdOut.strXML = m_pAccessPoint->GetStats( cmdOut.strData );
		bNotify = !cmdOut.strXML.empty();
	}
}




/////////////////////////////////////////////////////////////////////////////////////////
//UTL

long CLogicManager::ToNumberVal( const RTSTRING& strVal)
{
	long lVal(0);

	RTSTRING str = strVal;

	if ( !str.empty() )
	{
		size_t nPos = str.find(NO_SUCH_FILE);
		if ( nPos!=std::string::npos)
			return 0;

		size_t i=0;

		for (i=0;i<str.size();i++)
		{
			if( isdigit( str[i]) ) break; 
		}

		if ( i )
		{
			str.erase(0, i);
		}

		lVal = atol(str.c_str());
	}

	return lVal;
}

long CLogicManager::ToNumberValIfLineHave( const RTSTRING& strData,  const RTSTRING& strPattern)
{
	return ToNumberVal( GetStringWithPattern(strData,strPattern) );
}

RTSTRING CLogicManager::GetStringWithPattern(const RTSTRING& strDataOriginal,  const RTSTRING& strPattern, bool bReturnWitoutPattern/*= false*/)
{
	size_t nPos(0), nPosLast(0);

	RTSTRING str;
	RTSTRING strData(strDataOriginal+"\n");

	if ( !strData.empty() )
	{
		size_t pos = strData.find(NO_SUCH_FILE);
		if ( pos!=std::string::npos)
			return 0;

		std::stringstream sIn( strData.c_str() );

		char buf[512];

		while ( !sIn.getline(buf, sizeof(buf)).eof() )
		{
			str = buf;

			size_t nPosPattern = str.find( strPattern);
				
			if ( std::string::npos!=nPosPattern )
			{
				if ( bReturnWitoutPattern )
				{
					str.erase(0, nPosPattern+strPattern.size());

					//remove spaces
					while( !str.empty() )
					{
						if ( isspace( str[0]) )
						{
							str.erase(0, 1);
						}
						else
						{
							break;
						}
					}

					for (size_t i=0;i<str.size();i++)
					{
						if ( str[i]=='\n' || str[i]=='\r' )
						{
							str[i] = ' ';
						}
					}
				}

				return str;
			}
		}
	}

	//assert(0);

	return "";
}

RTSTRING CLogicManager::GetNextStringWithPattern(const RTSTRING& strData,  const RTSTRING& strPattern)
{
	size_t nPos(0), nPosLast(0);

	RTSTRING str;

	if ( !strData.empty() )
	{
		size_t pos = strData.find(NO_SUCH_FILE);
		if ( pos!=std::string::npos)
			return 0;

		bool bFound(false);

		do
		{
			nPos = strData.find('\n', nPos);

			if ( std::string::npos!=nPos)
			{
				str.assign(strData.c_str()+nPosLast, nPos-nPosLast);

				if ( bFound )
					return str;
				
				if ( std::string::npos!=str.find( strPattern) )
				{
					bFound = true;
				}
			}

			nPos++;

			nPosLast=nPos;
		}while( nPos );
	}

	assert(0);

	return "";
}



PROCTABLEWITHHEADER CLogicManager::ParseTableWithHeaders( const RTSTRING& strData, unsigned int nMinFielsInHeader, const char* szValTempl )
{
	size_t tPos, tHeader, tHeaderNext;
	size_t nStart=0;

	PROCTABLEWITHHEADER table;
	
	do
	{
		tPos    = strData.find('\n', nStart);
		tHeader = strData.find('|', nStart);
		tHeaderNext = strData.find('|', tPos);
		
		RTSTRING strLine = strData.substr(nStart, tPos-nStart );

		if ( strLine.empty() )
		{
			break;
		}
		
		//Parse data
		if ( !table.headers.empty() )
		{
			TABLEROW row;
			char * pch = strtok ((char*)strLine.c_str(), szValTempl);
			
			while (pch != NULL)
			{
				row.push_back(pch);
			    pch = strtok (NULL, szValTempl);
			}

			if ( row.size() >= table.headers.size() )
			{
				table.rows.push_back( row );
			}
			else
			{
				#ifdef _DEBUG
				fprintf(stderr, "Invalid Row\n" );
				#endif
			}
		}
		
		//Parse last header
		if ( ((std::string::npos!=tHeader		 &&
			  std::string::npos== tHeaderNext )  ||
			 (std::string::npos==tHeader &&
			  std::string::npos==tHeaderNext ) ) &&
			  table.headers.empty() )
		{
			const char* szTempl =" |\n\r\t";
			
			char * pch = strtok ((char*)strLine.c_str(), szTempl);
			
			while (pch != NULL)
			{
				table.headers.push_back(pch);
			    pch = strtok (NULL, szTempl);
			}

			//Remove second word from header name IP address->IP
			if ( std::string::npos==tHeader &&
			     std::string::npos==tHeaderNext )
			{
				bool bFound;

				do
				{
					bFound = false;

					for( TABLEHEADERS::iterator iCur=table.headers.begin();
						iCur!=table.headers.end();iCur++ )
					{
						std::string strHeader = *iCur;

						if ( islower(*strHeader.c_str()) )
						{
							bFound = true;

							//Restore correct header name
							if ( iCur!=table.headers.begin() )
							{
								std::string strHeaderPrev = *(iCur-1);
								strHeaderPrev+=" ";
								strHeaderPrev+=strHeader;

								*(iCur-1) = strHeaderPrev;
							}

						}
						
						if ( bFound )
						{
							table.headers.erase( iCur );
							break;
						}
					}
				}while( bFound );
			}

			if ( table.headers.size()<nMinFielsInHeader )
			{
				table.headers.erase( table.headers.begin(), table.headers.end() );
			}
		}
		
		nStart=tPos+1;
	}
	while( std::string::npos!=tPos );
	
	return table;
}

bool CLogicManager::IsDelimiter( const char c, const char* szTempl )
{
	while( *szTempl!=0 )
	{
		if (c==*szTempl++) return true;
	}

	return false;
}

PROCTABLEWITHHEADER CLogicManager::ParseTable( const RTSTRING& strData, FILTERPROC proc/*=NULL*/, const char* szValTempl/* =" :\n\r\t"*/  )
{
	PROCTABLEWITHHEADER table;

	std::stringstream sIn( strData+"\n" );

	char buf[512];

	while ( !sIn.getline(buf, sizeof(buf)).eof() )
	{
		size_t tRowCount(0);
		std::string strRowVal;
		TABLEROW row;

		std::string str = buf;

		for( size_t i=0;i<str.size();i++ )
		{
			if ( IsDelimiter( str[i], szValTempl ) )
			{
				if ( !strRowVal.empty() )
				{
					row.push_back( strRowVal );
				}

				strRowVal="";
				continue;
			}

			strRowVal+=str[i];
		}

		if ( !strRowVal.empty() )
		{
			row.push_back( strRowVal );
		}

		if ( proc  && proc(row) )
		{
			table.rows.push_back(row);
		}
	}
	
	return table;
}

wxString CLogicManager::GeValFromTable(const PROCTABLEWITHHEADER& table, size_t stIDX, size_t tRow)
{
	wxString strRes;
	
	try
	{
		const TABLEROW& row = table.rows[tRow];

		std::string strVal = row[stIDX];
		strRes = wxString::FromAscii(strVal.c_str());
	}
	catch(...)
	{
	}
	
	return strRes;
}

size_t CLogicManager::GeValFromTableInt(const PROCTABLEWITHHEADER& table, size_t stIDX, size_t tRow )
{
	unsigned long lVal(0);

	wxString str = GeValFromTable(table, stIDX, tRow );

	str.ToULong(&lVal);

	return (size_t)lVal;
}


size_t CLogicManager::GeValFromTableInt(const PROCTABLEWITHHEADER& table, const char* szHeader, size_t tRow, size_t tHeaderNameDupIDX )
{
	unsigned long lVal(0);

	wxString str = GeValFromTable(table, szHeader, tRow, tHeaderNameDupIDX );

	str.ToULong(&lVal);

	return (size_t)lVal;
}


wxString CLogicManager::GeValFromTable(const PROCTABLEWITHHEADER& table, const char* szHeader,
			size_t tRow, size_t tHeaderNameDupIDX )
{
	wxString strRes;
	size_t tHeadeCurDupPos=0;
	size_t tHeaderIDX=0;
	
	try
	{
		TABLEHEADERS::const_iterator iHeader;
		
		for ( iHeader = table.headers.begin();
				iHeader!= table.headers.end();iHeader++, tHeaderIDX++ )
		{
			if ( *iHeader == szHeader )
			{
				if ( tHeadeCurDupPos == tHeaderNameDupIDX )
				{
					break;
				}
				
				tHeadeCurDupPos++;
			}
		}
		
		if ( iHeader!= table.headers.end() )
		{
			const TABLEROW& row = table.rows[tRow];
			std::string strVal = row[tHeaderIDX];
			
			strRes = wxString::FromAscii(strVal.c_str());
		}
	}
	catch(...)
	{
		printf("UUUUppsss\n");
		
	}
	
	return strRes;
}

size_t CLogicManager::StringToDigit(const std::string& strVal, unsigned long long vals[], size_t tMax )
{
	size_t nCunt(0);
	bool bSeekDigit = true;

	for (size_t i=0;i<tMax;i++)
		vals[i] = 0;

	const char* pRound = strVal.c_str();

	do
	{
		if ( bSeekDigit )
		{
			if ( isdigit(*pRound) )
			{
				#ifdef LINUX
				unsigned long long l(0);
				sscanf(pRound,"%Lu", &l );
				vals[nCunt++] = l;
				#else
				vals[nCunt++] = _atoi64(pRound);
				#endif

				if (nCunt>=tMax)
				{
					break;
				}

				bSeekDigit = false;
			}
		}
		else if ( !isdigit(*pRound) && *pRound!='.' )
		{
			bSeekDigit = true;
		}
	}while(*++pRound!=0);

	return nCunt;
}

/*
SSID: "SOREK40"
Mode: Managed   RSSI: 0 dBm     noise: 0 dBm    Channel: 11
BSSID: 00:26:5A:DE:0B:E5        Capability: ESS ShortSlot
Supported Rates: [ 1(b) 2(b) 5.5(b) 6 9 11(b) 12 18 24 36 48 54 ]
*/
bool CLogicManager::ExtractPairs( const char* szDelim, const RTSTRING& strData, NAMEDVALS& lst,
								 const char* szStartFrom/*=NULL*/)
{
	size_t nPos = szStartFrom?strData.find( szStartFrom ):0;

	if ( std::string::npos == nPos )
		return false;

	std::stringstream sIn( strData.substr(nPos, strData.size())+"\n" );

	char buf[512];

	bool bLookForName;

	while ( !sIn.getline(buf, sizeof(buf)).eof() )
	{
		//skip found line
		if ( nPos && nPos!=std::string::npos )
		{
			nPos = 0;
			continue;
		}

		size_t stLen = strlen(buf);
		bLookForName = true;

		NAMEDVAL val;
		
		size_t i=0;

		for (;i<stLen;i++)
		{
			if ( !isspace(buf[i]) )
			{
				break;
			}
		}

		char* p  = buf+i;

		for (;i<stLen;i++)
		{
			if ( bLookForName )
			{
				if ( NULL!=strchr(szDelim,buf[i]) )
				{
					buf[i++] = '\0';
					
					val.strLabel = p;

					if (val.strLabel.empty())
						continue;

					bLookForName = false;
					
					for (;i<stLen;i++)
					{
						if ( !isspace(buf[i]) )
						{
							--i;
							break;
						}
					}
				}
			}
			else
			{
				if ( isspace(buf[i]) )
				{
					for (size_t j=i+1;j<stLen;j++)
					{
						if ( !isspace(buf[j]) )
						{
							//found next kw
							if ( j-i>=3 &&
								 NULL!=strpbrk(buf+j,szDelim) )
							{
								if (!val.strLabel.empty() && !val.strVal.empty() )
								{
									lst.push_back( val );
									val.strLabel="";
									val.strVal="";
								}

								bLookForName = true;
								i=j;
								p  = buf+i;
							}

							break;
						}
					}
				}

				if (!bLookForName)
				{
					val.strVal.push_back(buf[i]);
				}
			}
		}

		if (!val.strLabel.empty() && !val.strVal.empty() )
		{
			lst.push_back( val );
			val.strLabel="";
			val.strVal="";
		}
	}

	return !lst.empty();
}

CLogicManager::INTLIST	CLogicManager::ParseDataTable(const RTSTRING& strData, const char* szTableStart, transform_fn f)
{
	INTLIST lst;

	std::stringstream sIn( strData+"\n" );

	char buf[512], digit[32];

	bool bFound(false);

	while ( !sIn.getline(buf, sizeof(buf)).eof() )
	{
		if ( NULL!=strstr(buf, szTableStart) )
		{
			bFound = true;
			continue;
		}

		if ( bFound )
		{
			if ( isupper(buf[0]) || 0==strlen(buf) )
			{
				break;
			}

			std::stringstream sLine( buf );

			while ( !sLine.getline(digit, sizeof(digit),' ').eof() )
			{
				unsigned char c = f( digit );
				lst.push_back( c );
			}
		}
	}

	return lst;
}

//////////////////////////////////////////////////////////////////////////
//no exception here
void CLogicManager::ReportThreadExit(cExcReason reason)
{
	OutputDebugStr(_T("CLogicManager::ReportThreadExit() Begin()\n"));

	wxCommandEvent eventCustom(wxEVT_THREAD_EXIT);
	eventCustom.SetId(reason);
	eventCustom.SetInt(reason);

    wxPostEvent(&m_wMain, eventCustom);

	wxThread::Sleep(10);

	OutputDebugStr(_T("CLogicManager::ReportThreadExit() End()\n"));
}


void CLogicManager::OnExecuteWirelessXML(const wxString& strScript, COMMANDRESPONSE& cmdOut, bool& bNotify)
{
	bNotify = false;

	if ( m_pAccessPoint )
	{
		std::string strXML = (const char*)strScript.ToAscii();

		m_pAccessPoint->ExecuteXML( strXML );

		//Get modem stats
		OnGetWirelessStat(cmdOut, bNotify);
	}
}

void CLogicManager::GetSection(const std::string& strStatData, const char* szPattern, NAMEDVALS& lstRes)
{
	NAMEDVALS lst;

	size_t stPos = strStatData.find( szPattern );

	if ( stPos!=std::string::npos )
	{
		size_t stPosEnd = strStatData.find( '[', stPos+1 );

		if ( stPosEnd!=std::string::npos )
		{
			std::string strSection = strStatData.substr(stPos, stPosEnd-stPos);
			CLogicManager::ExtractPairs(":", strSection, lst);
		}
	}

	for (size_t i=0;i<lst.size();i++)
	{
		lstRes.push_back( lst[i]);
	}
}

